/**
 *
 * @file webserver.c
 *
 * @brief This file contains the built in http server
 *
 * $Id: webserver.c 134 2009-01-21 12:12:23Z henri.doreau $
 */

/*
 * This file is part of Tsar.
 *
 * Tsar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tsar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tsar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include <utils.h>
#include <tsar.h>
#include <webserver.h>
#include <http_server.h>


int start_webserver( tsar_t * session, opt_t * o )
{
    int retVal;
    int sockFd = -1;

    print( MESSAGE_NOTICE, "Starting built in HTTP server...\n" );
    /* Attach server to an address:port */
    if ( bind_server( o, &sockFd ) )
    {
        print_error( "Failed to launch http server!\n" );
        retVal = 1;
    }
    /* Listen for incoming connections */
    else if ( listen( sockFd, MAX_CONNECTIONS ) )
    {
        perror( "listen" );
        retVal = 2;
    }
    else
    {
        print( MESSAGE_INFO, "HTTP server successfully launched and bound on port %hu\n",
                o->httpServerPort );
        retVal = 0;
    }
    session->webserverSock = sockFd;

    return retVal;
}

int bind_server( opt_t * o, int * sockFd )
{
    char httpPort[6] = "";
    int getaddrErrCode, retVal;
    struct addrinfo hints, * result = NULL;

    memset( &hints, 0x00, sizeof( hints ) );
#if defined( IPV4_ONLY )
    hints.ai_family   = AF_INET;
#elif defined( IP6_ONLY )
    hints.ai_family   = AF_INET6;
#else
    hints.ai_family   = AF_UNSPEC;      /* use IPv4 or IPv6 according to host configuration */
#endif
    hints.ai_socktype = SOCK_STREAM;    /* TCP */
    hints.ai_flags    = AI_PASSIVE | AI_ADDRCONFIG; /* Bind on local IP address | return IPvX addresses
                                                     * only if IPvX is configured on local machine */
    /* getaddrinfo takes a null terminated string that descripe the service to bind the socket to (here a port number)*/
    snprintf( httpPort, sizeof( httpPort ), "%hu", o->httpServerPort );
    if ( ( getaddrErrCode = getaddrinfo( NULL, httpPort, &hints, &result ) ) != 0 )
    {
        print_error( "%s\n", gai_strerror( getaddrErrCode ) );
        retVal = 1;
    }
    else if ( ( *sockFd = open_socket( result, httpPort ) ) == -1 )
    {
        /* We already printed an error message out */
        retVal = 2;
    }
    else
    {
        int one = 1;

        /* required to re-launch the program without having to waot 5mn (rfc guys were drunk...) */
        setsockopt( *sockFd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof( one ) );
        /* Allright! */
        retVal = 0;
    }

    if ( result != NULL )
        freeaddrinfo( result );

    return retVal;
}

int open_socket( struct addrinfo * res, const char * httpPort )
{
    int sockFd;
    struct addrinfo * rp;

    /* For every address returned by getaddrinfo() */
    for ( rp = res; rp != NULL; rp = rp->ai_next )
    {
        debug( "%s : trying to open and bind...\n", __FUNCTION__ );
        /* Open socket */
        if ( ( sockFd = socket( rp->ai_family, rp->ai_socktype, rp->ai_protocol ) ) < 0 )
        {
            /* We'll try other addresses... */
            continue;
        }
        /* Try to bind it */
        else if ( bind( sockFd, rp->ai_addr, rp->ai_addrlen ) == 0 )
        {
            /* Successfully opened and bound */
            print( MESSAGE_NOTICE, "HTTP server successfully bound on port %s\n", httpPort );
            break;
        }
        else
        {
            /* sockpool uses non blocking sockets */
            set_sock_unblock( sockFd );
        }
    }
    /* No one seemed to work... */
    if ( rp == NULL )
    {
        print_error( "Cannot bind the http server\n" );
        sockFd = -1;
    }

    return sockFd;
}

void shutdown_webserver( tsar_t * session )
{
    unsigned int i;

    print( MESSAGE_NOTICE, "Turning http server off\n" );
    /* We are smart people... */
    for ( i = 0; i < MAX_CONNECTIONS; i++ )
    {
        if ( session->webserverClients[i] != -1 )
        {
            shutdown( session->webserverClients[i], SHUT_RDWR );
            close( session->webserverClients[i] );
            session->webserverClients[i] = -1;
        }
    }
    if ( session->webserverSock != -1 )
    {
        shutdown( session->webserverSock, SHUT_RDWR );
        close( session->webserverSock );
        session->webserverSock = -1;
    }
}

/* this is a sockpool callback */
void webserver_accept( int sockFd, void * datas )
{
    int clientSock;
    unsigned int i;
    struct sockaddr_in client;
    socklen_t len;
    tsar_t * session;

    session = ( tsar_t * )datas;

    len = sizeof( client );
    if ( ( clientSock = accept( sockFd, ( struct sockaddr * )&client, &len ) ) == -1 )
        fatal( "Cannot accept a new connection!\n" );

    /*  Connection acepted */
    print( MESSAGE_CRITICAL, "Received incoming tcp connection from %s\n", 
            inet_ntoa( client.sin_addr ) );
    /* Store client socket */
    for ( i = 0; i < MAX_CONNECTIONS; i++ )
    {
        if ( session->webserverClients[i] == -1 )
        {
            session->webserverClients[i] = clientSock;
            debug( "Registering event for the client socket\n" );
            /* Register a callback to deal with incoming http requests on this socket */
            sockpool_add_event(
                    session->pool,          /* Our previously initialized events handler */
                    EVENT_TYPE_READ,        /* Exec callback when datas are readable on our socket */
                    &webserver_http_reply,  /* Callback to execute */
                    clientSock,             /* socket to watch */
                    ( void * )session );    /* Pass this pointer to the callback */
            return;
        }
    }

    print( MESSAGE_CRITICAL, "Too much connections opened (%u/%u), cannot accept this one!\n", 
            MAX_CONNECTIONS, MAX_CONNECTIONS );
    close( clientSock );
}

/* this is a sockpool callback */
void webserver_http_reply( int sockFd, void * datas )
{
    unsigned char buff[SNAPLEN] = "";
    ssize_t bytes;
    tsar_t * session;
    http_t * httpReply;
    struct sockaddr_in client;
    socklen_t clientAddrLen = sizeof( client );

    session = ( tsar_t * )datas;
    
    set_sock_block( sockFd );    /* we need to set blocking mode to ensure that tsar can capture every tcp fragments (on BSD at least) */

    if ( ( bytes = recvfrom( sockFd, buff, sizeof( buff ) - 1, 0, 
                    ( struct sockaddr * )&client, &clientAddrLen ) ) < 0 )
    {
        Perror( "recv" );
    }
    else if ( bytes == 0 )
    {
        unregister_closed_connection( sockFd, session );
    }
    else if ( ( httpReply = http_server_reply ( ( char * )buff, bytes, session->dumpFilename ) ) == NULL )
    {
        print( MESSAGE_ALERT, "Unreplied http request from %s\n",
                inet_ntoa( client.sin_addr ) );
    }
    else
    {
        long i;

        if ( ( i = send( sockFd, httpReply->entireResponse, httpReply->replyLen, 0 ) ) < 0 )
        {
            Perror( "send" );
            unregister_closed_connection( sockFd, session );
        }
        else
        {
            debug( "======== %lu bytes sent ========\n\n", i );
            set_sock_unblock( sockFd );
        }

        /* Free http reply crafting allocated memory */
        http_destroy_handle( httpReply );
    }

}

void unregister_closed_connection( int sockFd, tsar_t * session )
{
    unsigned int i;

    /* Connection must have been closed by the client */
    sockpool_delete_event( session->pool, sockFd );

    for ( i = 0; i < MAX_CONNECTIONS; i++ )
    {
        if ( session->webserverClients[i] == sockFd )
        {
            /* Unregister the connection */
            session->webserverClients[i] = -1;
            /* So we close the socket too... */
            close( sockFd );
            break;
        }
    }
}

void set_sock_unblock( int fd )
{
    char o = 1;

    ioctl( fd, FIONBIO, &o );
    usleep( IOCTL_IDLE_USEC );
}

void set_sock_block( int fd )
{
    char o = 0;

    ioctl( fd, FIONBIO, &o );
    usleep( IOCTL_IDLE_USEC );
}

